package com.ideaaedi.commonspring.lock;

import com.ideaaedi.commonds.exception.ValidateException;
import com.ideaaedi.commonds.function.NoArgConsumer;
import com.ideaaedi.commonds.function.NoArgFunction;
import com.ideaaedi.commonds.lock.RedisLockSupport;
import com.ideaaedi.commonds.validate.Validator;
import com.ideaaedi.commonspring.parser.SpelUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.lang.NonNull;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * redis分布式锁默认实现
 * <p>
 *     使用示例见{@link com.ideaaedi.commonspring.RedisLockSupportTest}
 * </p>
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 2022/4/19 10:08
 */
@Getter
public class DefaultRedisLockSupport implements RedisLockSupport {
    
    /** 获取锁时，不等待，获取不到直接失败 */
    public static final long NO_WAIT = 0L;
    
    /** 默认的redisson客户端 */
    private static volatile RedissonClient defaultRedissonClient;
    
    /** redisson客户端（优先级高于defaultRedissonClient，当redissonClient不为null时，使用redissonClient） */
    protected RedissonClient redissonClient;
    
    /** 锁 key */
    protected final String lockKey;
    
    /**
     * 锁等待时长 （取值范围：waitTime >= 0）
     * <br />0：表示不等待
     */
    protected long waitTime = NO_WAIT;
    
    /**
     * 持有锁的最大时长 （取值范围：leaseTime > 0 || leaseTime == -1）
     * <br />-1：表示自动续期
     */
    protected long leaseTime = -1L;
    
    /** waitTime和leaseTime的时间单位 */
    protected TimeUnit unit = TimeUnit.SECONDS;
    
    public DefaultRedisLockSupport(String lockKey) {
        this.lockKey = lockKey;
    }
    
    public DefaultRedisLockSupport(RedissonClient redissonClient, String lockKey) {
        this.redissonClient = redissonClient;
        this.lockKey = lockKey;
    }
    
    public DefaultRedisLockSupport(String lockKey, long waitTime, long leaseTime) {
        this.lockKey = lockKey;
        this.waitTime = waitTime;
        this.leaseTime = leaseTime;
    }
    
    public DefaultRedisLockSupport(RedissonClient redissonClient, String lockKey, long waitTime, long leaseTime) {
        this.redissonClient = redissonClient;
        this.lockKey = lockKey;
        this.waitTime = waitTime;
        this.leaseTime = leaseTime;
    }
    
    public DefaultRedisLockSupport(String lockKey, long waitTime, long leaseTime, TimeUnit unit) {
        this.lockKey = lockKey;
        this.waitTime = waitTime;
        this.leaseTime = leaseTime;
        this.unit = unit;
    }
    
    public DefaultRedisLockSupport(RedissonClient redissonClient, String lockKey, long waitTime, long leaseTime, TimeUnit unit) {
        this.redissonClient = redissonClient;
        this.lockKey = lockKey;
        this.waitTime = waitTime;
        this.leaseTime = leaseTime;
        this.unit = unit;
    }
    
    @Override
    public <P, R> R exec(Function<P, R> function, P param) throws NotAcquiredRedisLockException {
        RedissonClient client = redissonClient();
        RLock lock = client.getLock(lockKey);
        boolean obtainLock = false;
        try {
            obtainLock = lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            // ignore
        }
        if (obtainLock) {
            try {
                return function.apply(param);
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
        throw new NotAcquiredRedisLockException(lockKey, waitTime, unit);
    }
    
    @Override
    public <R> R exec(NoArgFunction<R> function) throws NotAcquiredRedisLockException {
        RedissonClient client = redissonClient();
        RLock lock = client.getLock(lockKey);
        boolean obtainLock = false;
        try {
            obtainLock = lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            // ignore
        }
        if (obtainLock) {
            try {
                return function.apply();
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
        throw new NotAcquiredRedisLockException(lockKey, waitTime, unit);
    }
    
    @Override
    public <P> void voidExec(Consumer<P> consumer, P param) throws NotAcquiredRedisLockException {
        RedissonClient client = redissonClient();
        RLock lock = client.getLock(lockKey);
        boolean obtainLock = false;
        try {
            obtainLock = lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            // ignore
        }
        if (obtainLock) {
            try {
                consumer.accept(param);
                return;
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
        throw new NotAcquiredRedisLockException(lockKey, waitTime, unit);
    }
    
    @Override
    public void voidExec(NoArgConsumer consumer) throws NotAcquiredRedisLockException {
        RedissonClient client = redissonClient();
        RLock lock = client.getLock(lockKey);
        boolean obtainLock = false;
        try {
            obtainLock = lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            // ignore
        }
        if (obtainLock) {
            try {
                consumer.accept();
                return;
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
        throw new NotAcquiredRedisLockException(lockKey, waitTime, unit);
    }
    
    /**
     * 获取RedissonClient实例
     *
     * @return  RedissonClient实例
     */
    public RedissonClient redissonClient() {
        if (this.redissonClient != null) {
            return this.redissonClient;
        }
        if (DefaultRedisLockSupport.defaultRedissonClient != null) {
            return DefaultRedisLockSupport.defaultRedissonClient;
        }
        throw new IllegalStateException("There is not redissonClient available.");
    }
    
    /**
     * 是否有默认的Redisson客户端
     */
    public static boolean hasDefaultRedissonClient() {
        return DefaultRedisLockSupport.defaultRedissonClient != null;
    }
    
    /**
     * 初始化默认的Redisson客户端
     *
     * @param redissonClient
     *            Redisson客户端实例
     */
    public static void initDefaultRedissonClient(RedissonClient redissonClient) {
        if (DefaultRedisLockSupport.defaultRedissonClient != null && !DefaultRedisLockSupport.defaultRedissonClient.equals(redissonClient)) {
            throw new IllegalStateException("defaultRedissonClient already been initialized.");
        }
        synchronized (DefaultRedisLockSupport.class) {
            if (DefaultRedisLockSupport.defaultRedissonClient != null) {
                if (DefaultRedisLockSupport.defaultRedissonClient.equals(redissonClient)) {
                    return;
                }
                throw new IllegalStateException("defaultRedissonClient already been initialized.");
            }
            DefaultRedisLockSupport.defaultRedissonClient = redissonClient;
        }
    }
    
    /**
     * 方法级分布式锁
     */
    @Target(value = ElementType.METHOD)
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface Synchronized {
        
        /**
         * 锁 key （支持spel）
         * <pre>
         *  spel表达式（注：因为本方法中用到了{@link TemplateParserContext}， 所以需要使用#{}将原生的spel包起来，形成最终的表达式）
         *  1、获取属性示例：{@code #{#param1.fieldA} }
         *  2、调用方法示例：{@code #{#param2.methodA()} }
         *  3、调用spring-bean示例：{@code #{@userService.getUsername(#userId)}},
         *     注：使用之前需要初始化bean解析器{@link SpelUtil#initBeanResolver(BeanResolver)}
         *        初始化bean解析器示例：{@code SpelUtil.initBeanResolver(new BeanFactoryResolver(applicationContext)); }
         *  4、调用静态字段示例：{@code #{T(org.springframework.core.Ordered).HIGHEST_PRECEDENCE} }
         *     注：若调用的是java.lang.包下的类，可以不指定包名
         *  5、调用静态方法示例：{@code #{T(java.util.Objects).nonNull(#returnObj)} }
         *     注：若调用的是java.lang.包下的类，可以不指定包名
         *  6、枚举示例1：{@code {T(com.ideaaedi.demo.enums.CachePrefixEnum).USER_ACCOUNT_PHONE.name()} }
         *     注：和调用静态方法是一样的
         *  7、枚举示例2：{@code #{T(com.ideaaedi.demo.enums.CachePrefixEnum).USER_ACCOUNT_PHONE.key(#user.account, #user.phone)} }
         *     注：和调用静态方法是一样的
         *  8、判断示例1：{@code #{#code == 200} }
         *  9、判断示例2：{@code #{#user == null} }
         *  10、......
         *
         *  更多spel语法见{@link SpelUtil}
         * </pre>
         */
        String lockKey();
    
        /** 等待获取锁的最大时长 (0表示不等待, 直接获取，无论能否获取到) */
        long waitTime() default NO_WAIT;
    
        /** 释放锁的最大时长 */
        long leaseTime() default 3L;
    
        /** WaitTime和LeaseTime的时间单位 */
        TimeUnit unit() default TimeUnit.SECONDS;
    }
    
    /**
     * {@link DefaultRedisLockSupport.Synchronized}校验器
     *
     * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
     * @since 2022/5/17 14:29
     */
    @SuppressWarnings("AlibabaAbstractMethodOrInterfaceMethodMustUseJavadoc")
    public interface SynchronizedValidator extends Validator<Pair<Method, Synchronized>> {
        
        @Override
        default void validate() throws ValidateException{
            throw new UnsupportedOperationException();
        }
    
        @Override
        default void validate(Supplier<Pair<Method, Synchronized>> supplier, Function<Pair<Method, Synchronized>, Boolean> function) throws ValidateException{
            throw new UnsupportedOperationException();
        }
    
        @Override
        default boolean validateAndGet(){
            throw new UnsupportedOperationException();
        }
    
        @Override
        default boolean validateAndGet(Supplier<Pair<Method, Synchronized>> supplier){
            throw new UnsupportedOperationException();
        }
    
        @Override
        default boolean validateAndGet(Supplier<Pair<Method, Synchronized>> supplier, Function<Pair<Method, Synchronized>, Boolean> function){
            throw new UnsupportedOperationException();
        }
    }
    
    /**
     * 方法级分布式锁 aop 实现
     * <br />
     * <br />
     * 一般的业务需求下，分布式锁的aop优先级需要大于声明式事务@Transactional的。
     * <br />
     * <br />
     * 此AOP的优先级为<code>{@link Ordered#HIGHEST_PRECEDENCE} + 100</code>,
     * 而声明式事务@Transactional的优先级在{@link org.springframework.transaction.annotation.EnableTransactionManagement#order()}控制，其默认值为
     * {@link Ordered#LOWEST_PRECEDENCE}}，所以此AOP的优先级是大于声明式事务@Transactional的
     *
     * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
     * @since 2022/5/14 10:06
     */
    @Slf4j
    @Aspect
    public static class SynchronizedAdvice implements BeanPostProcessor, Ordered {
    
        public static final String BEAN_NAME = "synchronizedAdviceAdvice";
    
        /** aop order */
        public static int order = Ordered.HIGHEST_PRECEDENCE + 100;
        
        /** {@link DefaultRedisLockSupport.Synchronized}校验器, 项目启动时校验 */
        @Autowired(required = false)
        private SynchronizedValidator synchronizedValidator;
        
        /** 若redissonClient为null, 则会使用{@link DefaultRedisLockSupport#defaultRedissonClient} */
        @Autowired(required = false)
        private RedissonClient redissonClient;
        
        @Around("@annotation(synchronizedAnno)")
        public Object aroundAdvice(ProceedingJoinPoint thisJoinPoint, Synchronized synchronizedAnno) throws Throwable {
            String lockKeySpel = synchronizedAnno.lockKey();
            long waitTime = synchronizedAnno.waitTime();
            long leaseTime = synchronizedAnno.leaseTime();
            TimeUnit unit = synchronizedAnno.unit();
            Method method = ((MethodSignature) thisJoinPoint.getSignature()).getMethod();
            Object[] arguments = thisJoinPoint.getArgs();
            String lockKey;
            if (StringUtils.isNotBlank(lockKeySpel) && lockKeySpel.contains("#")) {
                lockKey = SpelUtil.parseSpel(method, arguments, String.class, lockKeySpel);
            } else {
                lockKey = lockKeySpel;
            }
            log.debug("lockKeySpel -> {}, lockKey -> {}, waitTime -> {}, leaseTime -> {}, unit -> {}, ", lockKeySpel, lockKey, waitTime, leaseTime, unit);
            RedissonClient client = redissonClient == null ? DefaultRedisLockSupport.defaultRedissonClient : redissonClient;
            Objects.requireNonNull(client, "redissonClient is null. Please provide redissonClient.");
            RLock lock = client.getLock(lockKey);
            boolean obtainLock = false;
            try {
                obtainLock = lock.tryLock(waitTime, leaseTime, unit);
            } catch (InterruptedException e) {
                // ignore
            }
            if (obtainLock) {
                try {
                    return thisJoinPoint.proceed();
                } finally {
                    if (lock.isHeldByCurrentThread()) {
                        lock.unlock();
                    }
                }
            }
            throw new NotAcquiredRedisLockException(lockKey, waitTime, unit);
        }
    
        @Override
        public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
            // 校验
            if (synchronizedValidator != null) {
                Class<?> clazz = bean.getClass();
                for (Method method : clazz.getMethods()) {
                    Synchronized annotation = AnnotationUtils.findAnnotation(method, Synchronized.class);
                    if (annotation != null) {
                        synchronizedValidator.validate(() -> Pair.of(method, annotation));
                    }
                }
            }
            return bean;
        }
    
        @Override
        public int getOrder() {
            return SynchronizedAdvice.order;
        }
    }
}